Skip to content

fix: release 환경 약관 동의 화면 버튼 눌렀을 때 응답 없음 해결#122

Merged
easyhooon merged 1 commit intodevelopfrom
BOOK-242-fix/#121
Aug 8, 2025
Merged

fix: release 환경 약관 동의 화면 버튼 눌렀을 때 응답 없음 해결#122
easyhooon merged 1 commit intodevelopfrom
BOOK-242-fix/#121

Conversation

@easyhooon
Copy link
Copy Markdown
Contributor

@easyhooon easyhooon commented Aug 8, 2025

release 환경 약관 동의 화면 버튼 눌렀을 때 응답 없음 해결

🔗 관련 이슈

📙 작업 설명

  • RepositoryImpl Unit 반환 TermsAgreementModel 반환으로 변경

🧪 테스트 내역 (선택)

  • 주요 기능 정상 동작 확인
  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

💬 추가 설명 or 리뷰 포인트 (선택)

Summary by CodeRabbit

  • 새로운 기능
    • 약관 동의 프로세스에 대한 상세 정보를 반환하도록 기능 개선
  • 모델 변경
    • 사용자 약관 동의와 관련된 새로운 데이터 모델 추가
  • 리포지토리 업데이트
    • 약관 동의 시 추가 정보를 제공하도록 리포지토리 로직 수정

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 8, 2025

Walkthrough

UserRepository의 agreeTerms 함수 반환 타입이 Result에서 Result로 변경되었습니다. 이를 위해 TermsAgreementModel 데이터 클래스가 새로 추가되고, TermsAgreementResponse를 TermsAgreementModel로 변환하는 매퍼 함수가 구현되었습니다. DefaultUserRepository의 구현도 이에 맞게 수정되었습니다.

Changes

Cohort / File(s) Change Summary
UserRepository 인터페이스 및 반환 타입 변경
core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/UserRepository.kt
agreeTerms 함수 반환 타입을 Result → Result로 변경, TermsAgreementModel import 추가
TermsAgreementModel 도메인 모델 추가
core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt
TermsAgreementModel 데이터 클래스 신설 (id, email, nickname, provider, termsAgreed 필드 포함)
응답→모델 매핑 함수 도입
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt
TermsAgreementResponse를 TermsAgreementModel로 변환하는 toModel() 함수 추가 및 관련 import 추가
DefaultUserRepository 구현 변경
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt
agreeTerms 함수 내 반환값을 service 응답에서 toModel()로 변환하여 반환하도록 구현 변경

Sequence Diagram(s)

sequenceDiagram
    participant UI
    participant UserRepository
    participant Service
    participant Mapper

    UI->>UserRepository: agreeTerms(termsAgreed)
    UserRepository->>Service: agreeTerms(termsAgreed)
    Service-->>UserRepository: TermsAgreementResponse
    UserRepository->>Mapper: toModel(TermsAgreementResponse)
    Mapper-->>UserRepository: TermsAgreementModel
    UserRepository-->>UI: Result<TermsAgreementModel>
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Assessment against linked issues

Objective Addressed Explanation
약관 동의 화면 버튼 눌렀을 때 응답 없음 버그 수정 (#121)
버그 재현/원인 분석/수정 및 테스트 (#121)

Assessment against linked issues: Out-of-scope changes

(해당 변경사항 중 범위를 벗어난 코드 변경은 발견되지 않았습니다.)

Poem

🐇
약관 동의, 이제는 확실히!
모델도 새로 생기고, 응답도 풍성히.
버튼 한 번에 결과가 쏙—
리뷰하는 토끼도 기분이 좋~
코드가 깔끔해진 오늘,
책방에 평화가 찾아오네!
📚✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-242-fix/#121

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions Bot added the 🐞 fix Something isn't working label Aug 8, 2025
@github-actions github-actions Bot requested a review from seoyoon513 August 8, 2025 16:54
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt (2)

3-9: KDoc 추가 제안
해당 모델의 의미(약관 동의 API 응답을 표현)와 사용처(agreeTerms 반환값)를 간단히 문서화하면 가독성과 유지보수성이 좋아집니다.

 package com.ninecraft.booket.core.model

+/**
+ * 약관 동의 API 성공 시 서버에서 내려주는 사용자 정보를 표현하는 도메인 모델.
+ * UserRepository.agreeTerms 의 반환 타입으로 사용됩니다.
+ */
 data class TermsAgreementModel(
     val id: String,
     val email: String,
     val nickname: String,
     val provider: String,
     val termsAgreed: Boolean,
 )

3-9: 중복 모델 재사용/정규화 검토
UserProfileModel 과 필드 구성이 동일합니다. 의미적 분리를 위해 별도 타입을 두는 선택도 타당하지만, 중복을 줄이고자 한다면:

  • UserProfileModel 재사용, 또는
  • 공통 UserBaseModel 추출 후 두 모델이 이를 확장
    을 고려할 수 있습니다. 현재는 선택 사항입니다.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c688ca4 and d98173e.

📒 Files selected for processing (4)
  • core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/UserRepository.kt (1 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt (3 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt (1 hunks)
  • core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.
📚 Learning: 2025-07-09T01:14:29.836Z
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

Applied to files:

  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt
  • core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (6)
core/model/src/main/kotlin/com/ninecraft/booket/core/model/TermsAgreementModel.kt (1)

3-9: LGTM — TermsAgreementModel 추가가 문제의 핵심(Any/Unit 직렬화 문제) 해결에 적합합니다.
모델 정의와 필드 구성이 일관적이며, 레이어 경계(도메인 모델)에도 잘 맞습니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt (2)

15-17: LGTM — Retrofit 직렬화 오류(Any/Object/Unit 컨버터 문제) 회피를 위한 반환 타입 전환이 올바르게 반영되었습니다.
서비스 응답을 즉시 toModel()로 변환해 Result을 반환하는 흐름이 Data 레이어의 일관성을 유지합니다.


15-17: ReedService.agreeTerms 반환 타입 및 서버 응답 상태 확인 요청

  • agreeTerms 시그니처가 suspend fun agreeTerms(@Body TermsAgreementRequest): TermsAgreementResponse 로 정의됨
  • TermsAgreementResponse(data class id, email, nickname, provider, termsAgreed) 필드 모두 non-nullable

⇒ Any/Unit 관련 컨버터 오류 가능성은 제거되었습니다.
다만 서버가 PUT /api/v1/users/me/terms-agreement 호출 시 200(또는 201) + JSON Body를 반환하는지 확인이 필요합니다.
– 204 No Content 응답일 경우 직렬화 실패가 재현될 수 있으므로, 빈 Body 대응(예: Unit 반환 또는 빈 JSON 지원)도 검토해주세요.

위 사항 확인 후 회신 부탁드립니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt (1)

242-250: LGTM — TermsAgreementResponse → TermsAgreementModel 매핑이 정확합니다.
필드 매핑(id/email/nickname/provider/termsAgreed) 누락/오타 없이 일치합니다.

core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/UserRepository.kt (2)

8-10: LGTM — 계약 변경이 명확하며, 도메인에 유용한 데이터가 상위로 전달됩니다.
Result → Result 변경으로 직렬화 문제를 해소하고, 호출자가 즉시 유용한 데이터를 활용할 수 있습니다.


8-10: 호출부 검증 완료: agreeTerms 변경에 따른 영향 없음

  • DefaultUserRepository.agreeTerms에서 runSuspendCatching으로 Result<TermsAgreementModel>을 반환
  • TermsAgreementPresenter(feature/login)에서 .onSuccess { … }로 반환값을 처리
  • 다른 곳에서 Result<Unit> 타입은 AuthRepository 계열 메서드에만 사용, agreeTerms에는 불필요

위 호출부 모두 반환값을 무시하거나 패턴 매칭 없이 사용한 곳이 없어 컴파일 에러·로직 누락 우려가 없습니다. 추가 조치 불필요합니다.

Comment on lines +242 to +250
internal fun TermsAgreementResponse.toModel(): TermsAgreementModel {
return TermsAgreementModel(
id = id,
email = email,
nickname = nickname,
provider = provider,
termsAgreed = termsAgreed,
)
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

널 안정성/타입 정합성 확인 및 매퍼 단위 테스트 권장

  • Response 쪽 필드가 모두 non-null String/Boolean 인지 확인 바랍니다(Nullable 이면 컴파일/런타임 이슈 발생 가능).
  • 간단한 매퍼 단위 테스트를 추가해 회귀를 방지하는 것을 권장합니다.

다음 스크립트로 Response 정의의 필드 타입을 빠르게 확인하세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] TermsAgreementResponse 정의 및 필드 타입 확인"
rg -n --pretty --hidden --glob '!*build*' 'data\s+class\s+TermsAgreementResponse' -A 20 -B 2 || true

echo
echo "[2] 매퍼 호출 경로 확인(실제 사용 여부)"
rg -n --pretty --hidden --glob '!*build*' 'toModel\(\)\s*$' -A 1 -B 1 | rg -n 'TermsAgreementResponse' -A 1 -B 1 || true

Length of output: 840


TermsAgreementResponse 매퍼 null 안정성 확인 완료 및 테스트 추가 권장

  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/TermsAgreementResponse.kt 의 id, email, nickname, provider는 모두 non-null String, termsAgreed는 non-null Boolean임을 확인했습니다.
  • 현재 코드베이스에서 toModel() 호출 경로가 검색되지 않으므로, 실제 사용 여부를 검토하고 사용하지 않는다면 매퍼 제거를 고려해주세요.
  • TermsAgreementResponse → TermsAgreementModel 매핑 로직에 대한 단위 테스트(예: 다양한 입력 값에 대한 필드 매핑 검증)를 추가해 회귀를 방지할 것을 권장합니다.
🤖 Prompt for AI Agents
In
core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt
around lines 242 to 250, verify if the toModel() extension function for
TermsAgreementResponse is actually used in the codebase; if not, consider
removing it to reduce dead code. Additionally, add unit tests for this mapping
function to ensure all fields are correctly mapped from TermsAgreementResponse
to TermsAgreementModel, covering various input scenarios to prevent regressions.

Copy link
Copy Markdown
Contributor Author

@easyhooon easyhooon Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai 너 혹시 문제 원인 암?

TermsAgreementPresenter에서 agreeTerms 함수 호출시 위 문제 발생했었음

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# ReedService의 agreeTerms 메서드 정의 확인
echo "=== ReedService의 agreeTerms 메서드 정의 확인 ==="
fd -e kt "ReedService" --exec grep -l "agreeTerms" {} \; | head -5 | xargs cat 2>/dev/null | grep -A 3 -B 3 "agreeTerms"

echo -e "\n=== TermsAgreementResponse 클래스 정의 확인 ==="
fd -e kt "TermsAgreementResponse" --exec cat {} \; | head -50

Length of output: 1076


@easyhooon 네, 문제 원인을 알고 있습니다!

이 에러는 Retrofit과 kotlinx.serialization을 함께 사용할 때 Unit 반환 타입을 처리하지 못해서 발생한 문제입니다.

문제 원인 분석

  1. Unit 타입의 특성: Kotlin의 Unit은 Java의 void와 유사하지만, 실제로는 객체입니다
  2. Serializer 부재: kotlinx.serialization은 Unit에 대한 기본 serializer를 제공하지 않습니다
  3. Retrofit의 처리: Retrofit이 응답을 역직렬화하려 할 때:
    • Unit에 대한 serializer를 찾을 수 없음
    • Any (Java의 Object)로 폴백 시도
    • Any에 대한 serializer도 없어서 최종적으로 실패

해결 방법

PR에서 적용한 해결책이 정확합니다:

  • 이전: suspend fun agreeTerms(...): Unit
  • 수정: suspend fun agreeTerms(...): TermsAgreementResponse

실제 응답 데이터 모델(TermsAgreementResponse)을 반환하도록 변경하여, kotlinx.serialization이 적절한 serializer를 찾을 수 있게 되었습니다.

대안적 해결 방법

만약 정말로 응답 본문이 필요 없다면:

  1. Response<Void> 사용
  2. ResponseBody 사용 후 무시
  3. Custom Call Adapter 구현

하지만 현재 API가 실제로 사용자 정보를 반환하므로, 현재 해결책이 가장 적절합니다!


✏️ Learnings added
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#122
File: core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt:242-250
Timestamp: 2025-08-08T17:23:23.791Z
Learning: Reed-Android 프로젝트에서 Retrofit과 kotlinx.serialization을 함께 사용할 때, API 메서드가 Unit을 반환하면 "Serializer for class 'Any' is not found" 에러가 발생한다. 이는 kotlinx.serialization이 Unit 타입에 대한 serializer를 제공하지 않기 때문이며, 실제 응답 모델을 반환하도록 변경하여 해결한다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.

Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트에서 core:model 모듈은 순수 Kotlin 모듈이므로 Android 리소스(R.string 등)에 접근할 수 없다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트는 현재 다국어 지원 계획이 없어서 모델에 한글 문자열을 직접 포함하는 것이 허용된다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.054Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.

Copy link
Copy Markdown
Contributor

@seoyoon513 seoyoon513 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정말 미스테리했던 버그.. Service에서 Response 타입 지정햇는데 repository에서 반환 타입을 Unit으로 했다고 터져버린 😢

@easyhooon
Copy link
Copy Markdown
Contributor Author

easyhooon commented Aug 8, 2025

@coderabbitai 너 혹시 문제 원인 암?

Unable to create converter for class java.lang.Object for method ReedService.agreeTerms
kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.
Please ensure that class is marked as '@serializable' and that the serialization compiler plugin is applied.
square/retrofit#4011

TermsAgreementPresenter에서 agreeTerms 함수 호출시 위 문제 발생했었음

@easyhooon easyhooon merged commit e5ad34d into develop Aug 8, 2025
5 checks passed
@easyhooon easyhooon deleted the BOOK-242-fix/#121 branch August 8, 2025 17:06
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 8, 2025

Tip

For best results, initiate chat on the files or code changes.

@easyhooon 원인 정리해드릴게요.

  • 발생 원인: Retrofit가 ReedService.agreeTerms 응답을 Any/Object로 디시리얼라이즈하려다 실패했습니다. kotlinx.serialization은 Any(java.lang.Object)에 대한 Serializer가 없어서, “Unable to create converter for class java.lang.Object … / Serializer for class 'Any' is not found.”가 터집니다. 이 상황은 반환/제네릭 타입이 Unit/Any로 귀결되거나, 상위 safeApiCall 같은 래퍼에서 T가 Any로 추론될 때 흔합니다.

  • 이번 PR의 핵심 Fix: agreeTerms가 실제 서버의 JSON을 담는 구체 타입(@serializable DTO: TermsAgreementResponse)을 받고, 이를 TermsAgreementModel로 매핑해 반환하도록 계약을 바꿨습니다. 즉 “애매한 Any/Unit” 대신 “구체 타입”을 사용하게 해 컨버터가 정상 동작합니다. (저장된 학습에 있는 Json { ignoreUnknownKeys = true ... } 설정과도 궁합이 맞습니다.)

추가 체크 포인트

  • ReedService.agreeTerms의 반환 타입이 실제로 TermsAgreementResponse(또는 Response)인지 확인.
  • DTO는 @serializable 유지.
  • safeApiCall 같은 헬퍼의 제네릭 T가 정확히 TermsAgreementResponse/TermsAgreementModel로 추론되도록 명시적 타입 인자 또는 반환식으로 유도.
  • 만약 진짜로 본문이 없는 API라면 204 + Response를 쓰거나, ResponseBody/JsonElement로 받아서 버리는 방식으로 Any를 피하는 것도 방법입니다.

필요하시면 서비스/레포 시그니처와 safeApiCall 제네릭 추론도 같이 점검해드릴게요.


🧠 Learnings used
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트는 현재 다국어 지원 계획이 없어서 모델에 한글 문자열을 직접 포함하는 것이 허용된다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트에서 core:model 모듈은 순수 Kotlin 모듈이므로 Android 리소스(R.string 등)에 접근할 수 없다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 8, 2025

Tip

For best results, initiate chat on the files or code changes.

@easyhooon 네, 문제 원인 알고 있습니다!

근본 원인:
Retrofit과 kotlinx.serialization 조합에서 Unit 타입 응답을 처리할 때 발생한 문제입니다.

  1. 기존 코드: agreeTerms()Result<Unit> 반환
  2. Retrofit이 서버 응답을 Unit으로 변환하려 시도
  3. kotlinx.serialization은 Unit/Any 타입에 대한 Serializer가 없음
  4. 결과: "Serializer for class 'Any' is not found" 에러 발생

해결 방법:
Unit 대신 구체적인 데이터 클래스 TermsAgreementModel을 반환하도록 변경했습니다. 이제 서버 응답(TermsAgreementResponse)을 제대로 파싱하고 모델로 변환할 수 있게 되었죠.

이는 Retrofit + kotlinx.serialization 조합에서 흔히 발생하는 이슈입니다. 서버가 빈 응답(204 No Content)을 보내는 게 아니라 실제 JSON 데이터를 반환하는 경우, 항상 구체적인 타입을 명시해야 합니다.


🧠 Learnings used
Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.054Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트는 현재 다국어 지원 계획이 없어서 모델에 한글 문자열을 직접 포함하는 것이 허용된다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: core/model/src/main/kotlin/com/ninecraft/booket/core/model/EmotionModel.kt:11-18
Timestamp: 2025-07-31T16:58:59.404Z
Learning: Reed-Android 프로젝트에서 core:model 모듈은 순수 Kotlin 모듈이므로 Android 리소스(R.string 등)에 접근할 수 없다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.

@easyhooon
Copy link
Copy Markdown
Contributor Author

@coderabbitai

그니까 니 말은

    @PUT("api/v1/users/me/terms-agreement")
    suspend fun agreeTerms(@Body termsAgreementRequest: TermsAgreementRequest): TermsAgreementResponse
internal class DefaultUserRepository @Inject constructor(
    private val service: ReedService,
    private val onboardingDataSource: OnboardingDataSource,
) : UserRepository {
    override suspend fun agreeTerms(termsAgreed: Boolean) = runSuspendCatching {
        service.agreeTerms(TermsAgreementRequest(termsAgreed))
        Unit
    }
    //...
}

retrofit interface에 Response를 제대로 작성해도 Repository 구현체에서 return을 Unit 으로 해버리면 release 환경에서 kotlinx serialization converter에게 Any 타입을 직렬화하게 만들어 직렬화 실패를 트리거할 수 있다는거야?(정확힌 Unit이긴한데 에러 문구엔 Any라고 언급이 되어있어서)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 8, 2025

Tip

For best results, initiate chat on the files or code changes.

@easyhooon 거의 맞는데, 약간의 차이가 있어요!

실제로는 이렇습니다:

  1. Retrofit 인터페이스는 정상: suspend fun agreeTerms(...): TermsAgreementResponse
  2. Repository에서 Unit 반환이 직접적 문제는 아님: Repository가 Unit을 반환하는 것 자체는 괜찮습니다.

진짜 문제는:
Result<Unit> 타입을 처리할 때 발생합니다. Retrofit이 응답을 파싱하려 할 때, Unit이 제네릭 타입 파라미터로 사용되면 타입 소거(type erasure) 때문에 런타임에 Any로 취급됩니다. kotlinx.serialization은 Unit/Any에 대한 기본 Serializer가 없어서 "Serializer for class 'Any' is not found" 에러가 발생합니다.

왜 Any로 표시되나요?

  • Kotlin의 Unit은 JVM에서 java.lang.Object(Kotlin의 Any)로 표현됨
  • 제네릭 타입 소거로 인해 Result<Unit>이 런타임에 Result<Object>가 됨
  • kotlinx.serialization이 이를 처리하려 할 때 "Any" 타입으로 인식

해결책:
서버가 실제 JSON 응답을 보내는데 이를 Unit으로 버리지 말고, 구체적인 모델(TermsAgreementModel)로 받아서 처리하는 것이 정답입니다. 이게 바로 이번 PR에서 수정한 내용이죠!


✏️ Learnings added
Learnt from: easyhooon
URL: https://github.com/YAPP-Github/Reed-Android/pull/122

Timestamp: 2025-08-08T17:16:02.800Z
Learning: Reed-Android 프로젝트에서 Retrofit + kotlinx.serialization 조합 사용 시, 서버가 JSON 응답을 반환하는 API에서 Repository가 `Result<Unit>`을 반환하면 런타임에 타입 소거로 인해 Unit이 Any(java.lang.Object)로 취급되어 "Serializer for class 'Any' is not found" 에러가 발생한다. 이를 해결하려면 구체적인 데이터 클래스를 반환 타입으로 사용해야 한다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.

Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/component/RecordItem.kt:29-37
Timestamp: 2025-07-31T23:17:40.054Z
Learning: Reed-Android 프로젝트에서는 API가 준비되지 않은 상황에서 UI를 먼저 구현하고, API 연동 시점에 하드코딩된 데이터를 실제 데이터로 교체하는 개발 방식을 사용한다. RecordItem 컴포넌트의 emotionTags 매개변수도 API 연동 시점에 `text = emotionTags.joinToString(separator = "·") { "#$it" }`로 적용될 예정이다.

@easyhooon
Copy link
Copy Markdown
Contributor Author

easyhooon commented Aug 8, 2025

진짜 문제는:
Result 타입을 처리할 때 발생합니다. Retrofit이 응답을 파싱하려 할 때, Unit이 제네릭 타입 파라미터로 사용되면 타입 소거(type erasure) 때문에 런타임에 Any로 취급됩니다. kotlinx.serialization은 Unit/Any에 대한 기본 Serializer가 없어서 "Serializer for class 'Any' is not found" 에러가 발생합니다.

@coderabbitai Repository 함수의 return 타입이 Result<Unit>이라서 문제인거야? Retrofit 응답을 파싱하는것과 Repository 함수의 반환타입의 상관관계를 아직 이해하지 못해서 질문해

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 8, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the ReedService interface to see the agreeTerms method signature
fd -e kt "ReedService" --exec grep -l "agreeTerms" {} \;

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Find and show the ReedService.agreeTerms method
ast-grep --pattern 'suspend fun agreeTerms($_): $_'

Length of output: 227


🏁 Script executed:

#!/bin/bash
# Look for safeApiCall or runSuspendCatching implementation
rg "runSuspendCatching|safeApiCall" -A 5 -B 2

Length of output: 18386


🏁 Script executed:

#!/bin/bash
# Check the TermsAgreementResponse class
fd "TermsAgreementResponse.kt" --exec cat {} \;

Length of output: 495


🏁 Script executed:

#!/bin/bash
# Show the ReedService.agreeTerms method signature
cat ./core/network/src/main/kotlin/com/ninecraft/booket/core/network/service/ReedService.kt | grep -A 2 -B 2 "agreeTerms"

Length of output: 335


@easyhooon 아, 좋은 질문이에요! 제가 설명을 명확하게 못 드렸네요.

Repository 반환 타입과 Retrofit 파싱은 직접적인 관계가 없습니다.

실제 흐름을 보면:

  1. Retrofit 파싱 (Service 레벨)

    // ReedService - Retrofit은 여기서 파싱함
    suspend fun agreeTerms(...): TermsAgreementResponse  // ✅ 정상 파싱됨
  2. Repository 구현 (이전 코드의 문제)

    override suspend fun agreeTerms(termsAgreed: Boolean) = runSuspendCatching {
        service.agreeTerms(TermsAgreementRequest(termsAgreed))  // Response 받음
        Unit  // ❌ 응답을 버리고 Unit 반환
    }

진짜 문제는:

  • Repository 인터페이스가 Result<Unit>을 선언했을 때
  • 어딘가(아마 Presenter나 ViewModel)에서 이를 처리하는 과정에서 제네릭 타입 추론 문제 발생
  • 타입 소거로 인해 UnitAny로 취급되면서 serialization 관련 에러 발생

즉:

  • Retrofit은 이미 제대로 파싱했음
  • 문제는 그 이후 타입 처리 과정에서 발생
  • Repository가 구체적인 타입(TermsAgreementModel)을 반환하도록 수정하여 해결

Repository 반환 타입이 Retrofit 파싱에 영향을 주는 게 아니라, 타입 시스템과 제네릭 처리 과정에서 문제가 발생한 거예요!


🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#0
File: :0-0
Timestamp: 2025-08-08T17:16:02.800Z
Learning: Reed-Android 프로젝트에서 Retrofit + kotlinx.serialization 조합 사용 시, 서버가 JSON 응답을 반환하는 API에서 Repository가 `Result<Unit>`을 반환하면 런타임에 타입 소거로 인해 Unit이 Any(java.lang.Object)로 취급되어 "Serializer for class 'Any' is not found" 에러가 발생한다. 이를 해결하려면 구체적인 데이터 클래스를 반환 타입으로 사용해야 한다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.

Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 8, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find ReedService interface to see the agreeTerms method signature
fd -e kt "ReedService" --exec grep -l "interface.*ReedService" {} \; | head -5 | xargs cat

# Look for safeApiCall or runSuspendCatching implementation
rg "fun.*safeApiCall|fun.*runSuspendCatching" -A 10

Length of output: 16273


@easyhooon 아니에요, 좀 더 정확히 설명할게요!

Repository의 return type (Result<Unit>)과 Retrofit 파싱은 직접적인 관계가 없습니다.

실제 흐름:

  1. Retrofit 레벨: ReedService.agreeTerms() → JSON을 TermsAgreementResponse로 성공적으로 파싱 ✅
  2. Repository 레벨: 파싱된 응답을 받아서 Unit으로 버리고 Result<Unit> 반환 ✅

그럼 왜 에러가 발생했나요?

에러 메시지를 다시 보면 "Unable to create converter for class java.lang.Object for method ReedService.agreeTerms"라고 나와있죠.

진짜 문제는 아마도:

  • 빌드 최적화 과정에서 컴파일러/ProGuard/R8이 코드를 분석할 때, Repository가 응답을 사용하지 않고 Unit으로 버리는 것을 감지
  • Release 빌드에서 불필요하다고 판단하여 TermsAgreementResponse의 serializer를 제거하거나 최적화
  • 결과적으로 Retrofit이 실행 시점에 converter를 찾지 못함

정리하면:

  • Repository의 Result<Unit>직접 Retrofit 파싱을 방해하는 건 아님
  • 하지만 간접적으로 빌드 최적화 과정에서 필요한 Serializer가 제거되는 원인이 될 수 있음
  • 해결책: 실제 응답 타입(TermsAgreementModel)을 명시적으로 사용하여 컴파일러가 Serializer를 유지하도록 함

이래서 Debug 빌드에선 정상 동작하지만 Release 빌드에서만 문제가 발생한 거예요!


🧠 Learnings used
Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#0
File: :0-0
Timestamp: 2025-08-08T17:16:02.800Z
Learning: Reed-Android 프로젝트에서 Retrofit + kotlinx.serialization 조합 사용 시, 서버가 JSON 응답을 반환하는 API에서 Repository가 `Result<Unit>`을 반환하면 런타임에 타입 소거로 인해 Unit이 Any(java.lang.Object)로 취급되어 "Serializer for class 'Any' is not found" 에러가 발생한다. 이를 해결하려면 구체적인 데이터 클래스를 반환 타입으로 사용해야 한다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:7-30
Timestamp: 2025-07-14T00:52:03.723Z
Learning: 사용자는 kotlinx.serialization에서 Json { ignoreUnknownKeys = true, isLenient = true, encodeDefaults = true, prettyPrint = true } 설정을 사용하여 API 응답의 견고성을 높이는 접근 방식을 취하고 있다.

Learnt from: easyhooon
PR: YAPP-Github/Reed-Android#46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.

Learnt from: seoyoon513
PR: YAPP-Github/Reed-Android#35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 fix Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-242/fix] release 환경 약관 동의 화면 버튼 눌렀을 때 응답 없음

2 participants